package com.pnk087.hr.testutils;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Assert;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;

public class SetterGetterInvoker<T> {

   private Log log = LogFactory.getLog(SetterGetterInvoker.class);

   private T testTarget;

   private ValueFactory valueFactory;

   /**
    * Constructor.
    *
    * @param testTarget Target to be tested.
    */
   public SetterGetterInvoker(T testTarget) {
      super();
      this.testTarget = testTarget;
      this.valueFactory = new ValueFactory();
   }

   /**
    * Invoke setters and getters.
    */
   public void invokeSettersAndGetters() {
      Class<? extends Object> targetClass = testTarget.getClass();
      Method[] methods = targetClass.getMethods();
      for (int i = 0; i < methods.length; i++) {
         Method method = methods[i];
         if (method.getName().startsWith("set")) {
            Class<?>[] parameterTypes = method.getParameterTypes();
            if (parameterTypes.length == 1) {
               Object testValue = valueFactory.createValue(parameterTypes[0]);
               try {
                  if (log.isDebugEnabled()) {
                     log.debug("invoke set method: " + method.toString() + " with argument: " + testValue);
                  }
                  method.invoke(testTarget, testValue);
                  if (testValue instanceof Boolean) {
                     invokeGetter(targetClass, testValue, "is" + method.getName().substring(3));
                  } else {
                     invokeGetter(targetClass, testValue, "get" + method.getName().substring(3));
                  }
               } catch (IllegalAccessException e) {
                  Assert.fail("failed to access setter method: " + method.toString() + " - " + e.getMessage());
               } catch (InvocationTargetException e) {
                  Assert.fail("failed to invoke setter method: " + method.toString() + " - " + e.getMessage());
               }
            }
         }
      }
   }

   /**
    * Invoke getters and setters except some specific ones.
    *
    * @param excludeMap
    * @author vengthai
    * @author DucQuoc
    */
   public void invokeSettersAndGetters(Map<String, String> excludeMap) {
      Class<? extends Object> targetClass = testTarget.getClass();
      Method[] methods = targetClass.getMethods();
      for (int i = 0; i < methods.length; i++) {
         Method method = methods[i];
         if (excludeMap.get(method.getName()) != null) {
            continue;
         }
         if (method.getName().startsWith("set")) {
            Class<?>[] parameterTypes = method.getParameterTypes();
            if (parameterTypes.length == 1) {
               Object testValue = valueFactory.createValue(parameterTypes[0]);
               try {
                  if (log.isDebugEnabled()) {
                     log.debug("invoke set method: " + method.toString() + " with argument: " + testValue);
                  }
                  method.invoke(testTarget, testValue);
                  if (testValue instanceof Boolean) {
                     invokeGetter(targetClass, testValue, "is" + method.getName().substring(3));
                  } else {
                     invokeGetter(targetClass, testValue, "get" + method.getName().substring(3));
                  }
               } catch (IllegalAccessException e) {
                  Assert.fail("failed to access setter method: " + method.toString() + " - " + e.getMessage());
               } catch (InvocationTargetException e) {
                  Assert.fail("failed to invoke setter method: " + method.toString() + " - " + e.getMessage());
               }
            }
         }
      }
   }

   /**
    * @param targetClass   Class of target object.
    * @param expectedValue Expected value.
    * @param getterName    Name of getter.
    */
   private void invokeGetter(Class<?> targetClass, Object expectedValue, String getterName) {
      try {
         Method getterMethod = targetClass.getMethod(getterName);
         if (log.isDebugEnabled()) {
            log.debug("invoke get method: " + getterMethod.toString());
         }
         Object retrievedValue = getterMethod.invoke(testTarget);
         Class<?> returnType = getterMethod.getReturnType();
         if (returnType.isPrimitive()) {
            Assert.assertEquals("return value of " + getterName + " incorrect", expectedValue, retrievedValue);
         } else {
            Assert.assertSame("return value of " + getterName + " incorrect", expectedValue, retrievedValue);
         }
      } catch (NoSuchMethodException ignore) {
         // ignore if getter does not exist
         if (log.isDebugEnabled()) {
            log.debug("getter does not exist: " + getterName);
         }
      } catch (IllegalAccessException e) {
         Assert.fail("failed to access getter method: " + getterName + " - " + e.getMessage());
      } catch (InvocationTargetException e) {
         Assert.fail("failed to invoke getter method: " + getterName + " - " + e.getMessage());
      }
   }

}
